#ifdef __QT5_TARGET

#include <QtSql/QSqlError>
#include <QtCore/QVariant>

#else

#include <QSqlError>
#include <QVariant>

#endif

#include "PatientManager.h"
#include "CouldNotExecQueryException.h"
#include "CouldNotCastException.h"

PatientManager::PatientManager(void)
{
	this->fileNr_Patient_mapping = new QHash<unsigned int, Patient*>();
}

PatientManager::~PatientManager(void)
{
	try
	{
		if(this->fileNr_Patient_mapping != NULL)
	{
		QHash<unsigned int,Patient*>::iterator it = fileNr_Patient_mapping->begin();
		while(it != fileNr_Patient_mapping->end())
		{
			if(it.value() != NULL)
			{
				delete it.value();
				it.value() = NULL;
			}
			it++;
		}
	
		delete fileNr_Patient_mapping;
		this->fileNr_Patient_mapping = NULL;
	}
	if(!visits.empty())
	{
		QHash<unsigned int, Visit*>::iterator it = visits.begin();
		while(it != visits.end());
		{
			if(it.value() != NULL)
			{
				delete it.value();
				it.value() = NULL;
			}
			it++;
		}
	}
	}catch(int i)
	{
		Exception e;
		e.setErrorCode(i);
		e.setMessage("Fatal error: memory corruption while deleting patient manager");
		throw e;
	}
}

/* The initialization will call the queryPatients, 
	which will fill the hash tables with values from the
	databe. */
int PatientManager::init()
{
	insertedIndeces.append(-1);
	insertedIndeces.append(-1);
	insertedIndeces.append(-1);
	return queryPatients();
}

/* If the patient list is empty querys the patients from the
database, otherwise will return the values of the hash */
QList<Patient*> PatientManager::getAllPatients()
{
	if(this->fileNr_Patient_mapping->empty())
	{
		queryPatients();
	}
	return this->fileNr_Patient_mapping->values();
}

/* If the names list is empty calls the queryPatients,
otherwise will simple return the ordered names list */
QStringList PatientManager::getAllPatientNames()
{
	if(fileNr_Patient_mapping->empty() || names.empty())
	{
		queryPatients();
	}
	return names;
}

QList<unsigned int> PatientManager::getAllIds()
{
	return this->file_numbers;
}

QList<QString> PatientManager::getAllPhones()
{
	return this->phones;
}

//If the file number doesn't exist, NULL will be returned.
const Patient* PatientManager::findPatientByFileNumber( unsigned int filenr)
{
		return this->fileNr_Patient_mapping->value(filenr,NULL);
}

const Patient* PatientManager::findPatientByPhone(const QString &phone)
{
	Patient* p = this->phone_Patient_mapping.value(phone,NULL);
	return p;
}

const Patient* PatientManager::findPatientByNameIndex(int index)
{
	return this->fileNr_Patient_mapping->value(this->nameIndex_fileNr_mapping.value(index),NULL);
}

int PatientManager::getNameIndexByFileNr(unsigned int fileno)
{
	return this->fileNr_nameIndex_reverseLookup.value(fileno,-1);
}

int PatientManager::getFileIndexByFileNr(unsigned int fileno)
{
	return this->fileNr_fileIndex_reverseLookup.value(fileno,-1);
}

int PatientManager::getPhoneIndexByFileNr(unsigned int fileno)
{
	return this->phoneIndex_reverseLookup.value(fileno,-1);
}

//TODO: Template!!!
int PatientManager::findIndexByName(const QString& name)
{
	int imin = 0;
	int imax = this->names.count()-1;
	int imid = 0;
	while(imax >= imin)
	{
		imid = imin + ((imax-imin) >> 1);
		if(this->names.at(imid).toLower() < name.toLower())
			imin = imid + 1;
		else if(this->names.at(imid).toLower() > name.toLower())
			imax = imid - 1;
		else
			return imid;
	}
	return imin;
}

int PatientManager::findIndexByFileNo(unsigned int fileNo)
{
	int imin = 0;
	int imax = this->file_numbers.count()-1;
	int imid = 0;
	while(imax >= imin)
	{
		imid = imin + ((imax-imin) >> 1);
		if(this->file_numbers.at(imid) < fileNo)
			imin = imid + 1;
		else if(this->file_numbers.at(imid) > fileNo)
			imax = imid - 1;
		else
			return imid;
	}
	return imin;
}

int PatientManager::findIndexByPhone(const QString &phone)
{
	int imin = 0;
	int imax = this->phones.count()-1;
	int imid = 0;
	while(imax >= imin)
	{
		imid = imin + ((imax-imin) >> 1);
		if(this->phones.at(imid) < phone)
			imin = imid + 1;
		else if(this->phones.at(imid) > phone)
			imax = imid - 1;
		else
			return imid;
	}
	return imin;
}

/* Upon adding a new patient, the datas will added to all
hash tables and inserted to the database */
QList<int> PatientManager::addPatient(Patient* p)
{
	if(!QSqlDatabase::database().transaction())
	{
		Exception e;
		e.setErrorCode(-253);
		e.setMessage("Could not initialize transaction.");
		throw e;
	}
	QSqlQuery query;
	query.prepare("insert into patient (file_nr,phone_nr,name,dob) values (:pfilenr,:pphone,:pname,:pdob)");
	query.bindValue(":pname",p->getName());
	query.bindValue(":pdob",p->getDate());
	query.bindValue(":pfilenr",p->getFilenr());
	query.bindValue(":pphone",p->getPhone());
	if(!query.exec())
	{
		CouldNotExecQueryException e;
		e.setMessage("Could not execute query. " + query.lastError().text());
		QSqlDatabase::database().rollback();
		throw e;
	}
	if(!QSqlDatabase::database().commit())
	{
		Exception e;
		e.setErrorCode(-252);
		e.setMessage("Could not commit database changes.");
		throw e;
	}
	
	// inserting into the hashes
	this->fillHashTables(p);
	return this->insertedIndeces;
}

/* Fill all hashes with database records from Patient table */
int PatientManager::queryPatients()
{
	//Select all patients from the database, first sorted by name...
	QSqlQuery query;
	query.prepare("select * from patient order by name");
	if(!query.exec())
	{
			CouldNotExecQueryException e;
			e.setMessage(query.lastError().text());
			throw e;
			return e.getErrorCode();
	}
	int i = 0;
	while(query.next())
	{
		bool ok = false;
		unsigned int id = query.value(0).toUInt(&ok);
		if(!ok)
		{
			CouldNotCastException ex;
			ex.setMessage("Could not convert database column to int");
			throw ex;
			return ex.getErrorCode();
		}

		QDate date = query.value(3).toDate();
		if(!date.isValid())
		{
			CouldNotCastException ex;
			ex.setMessage("Could not convert database column to date");
			throw ex;
			return ex.getErrorCode();
		}
		
		//Creating a new Patient object...
		Patient *p = new Patient(id,query.value(2).toString(),query.value(1).toString(),date);
		
		// inserting into the hashes
		this->fillHashTables(p);
	}
	return 0;
}

/* insert into all hash tables and lists, taking care of
the right ordering */
void PatientManager::fillHashTables(Patient* p)
{

	int index = this->findIndexByName(p->getName());
	this->nameIndex_fileNr_mapping.insert(index,p->getFilenr());
	names.insert(index,p->getName());
	insertedIndeces[0] = index;
	int last = this->fileNr_nameIndex_reverseLookup.size();
	for(int i = last;i > index;i--)
	{
		//int prev_key = this->fileNr_nameIndex_reverseLookup.key(i-1);
		unsigned int key = this->fileNr_nameIndex_reverseLookup.key(i-1);
		this->fileNr_nameIndex_reverseLookup[key] = i;
		//this->fileNr_nameIndex_reverseLookup[perv_key] = i-1;
	}

	this->fileNr_nameIndex_reverseLookup.insert(p->getFilenr(),index);
	//Determining where to insert
	index = this->findIndexByFileNo(p->getFilenr());

	fileNr_Patient_mapping->insert(p->getFilenr(),p);
	file_numbers.insert(index,p->getFilenr());

	last = this->fileNr_fileIndex_reverseLookup.size();
	for(int i = last;i > index;i--)
	{
		//int prev_key = this->fileNr_nameIndex_reverseLookup.key(i-1);
		unsigned int key = this->fileNr_fileIndex_reverseLookup.key(i-1);
		this->fileNr_fileIndex_reverseLookup[key] = i;
		//this->fileNr_nameIndex_reverseLookup[perv_key] = i-1;
	}

	this->fileNr_fileIndex_reverseLookup.insert(p->getFilenr(),index);
	insertedIndeces[1] = index;

	if(p->getPhone().isEmpty())
		return;

	this->phone_Patient_mapping.insert(p->getPhone(),p);
	index = this->findIndexByPhone(p->getPhone());
	
	last = this->phoneIndex_reverseLookup.size();

	for(int i = last;i > index;i--)
	{
		//int prev_key = this->fileNr_nameIndex_reverseLookup.key(i-1);
		unsigned int key = this->phoneIndex_reverseLookup.key(i-1);
		this->phoneIndex_reverseLookup[key] = i;
		//this->fileNr_nameIndex_reverseLookup[perv_key] = i-1;
	}
	this->phoneIndex_reverseLookup.insert(p->getFilenr(),index);
	insertedIndeces[2] = index;
	phones.insert(index,p->getPhone());
}

QDate getLastVisit(const Patient* p)
{
	return QDate();
}

void queryVisits(const Patient* p)
{
	//visits.clear();
	QSqlQuery query;
	query.prepare("select * from visit where patient_id=:pid order by visit_date");
	//query.bindValue(":pid",p->
}

QList<Visit*> PatientManager::getVisits()
{
	return QList<Visit*>();
}

QList<Visit*> PatientManager::getAllUnfinished()
{
	return QList<Visit*>();
}

Visit* PatientManager::getLastVisit()
{
	return NULL;
}

QList<Visit*> PatientManager::getVisits(int pos, int number)
{
	return QList<Visit*>();
}

QList<Visit*> PatientManager::getVisits(const QString& filter)
{
	return QList<Visit*>();
}

QList<Visit*> PatientManager::getVisits(QDate date)
{
	return QList<Visit*>();
}

QList<Visit*> PatientManager::getVisits(QDate from, QDate to)
{
	return QList<Visit*>();
}

Visit* PatientManager::getFirstVisit()
{
	return NULL;
}

QList<Visit*> PatientManager::getFollowUps()
{
	return QList<Visit*>();
}

Visit* PatientManager::getVisit(unsigned int visit_id)
{
	return NULL;
}


